package org.wendu.wdoa.common.token;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.ctc.wstx.shaded.msv.org_isorelax.verifier.Verifier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.wendu.wdoa.common.exception.NotAuthentificationException;
import org.wendu.wdoa.common.token.dao.TokenDao;

import java.util.Date;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component//将令牌工具类做为受spring管理的bean
public class TokenUtils {

    private static final String CLAIM_NAME_USERID = "CLAIM_NAME_USERID";
    private static final String CLAIM_NAME_USERNAME = "CLAIM_NAME_USERNAME";

    private static long expireTime = 30;//过期时间，单位：秒



    private static TokenDao tokenDao;
    private static StringRedisTemplate stringRedisTemplate;//redis操作模版
    private static Environment env;//spring环境对象

    @Autowired
    public void setTokenDao(TokenDao tokenDao){
        log.debug("TokenDao对象被注入TokenUtils中：{}",tokenDao);
        TokenUtils.tokenDao = tokenDao;
    }

    @Autowired
    public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
        TokenUtils.stringRedisTemplate = stringRedisTemplate;
    }

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext){
        TokenUtils.env = applicationContext.getEnvironment();
        String time = env.getProperty("wdoa.expire-time","30");
        try {
            expireTime = Long.parseLong(time);
        } catch (Exception e) {
           expireTime = 30;
        }
        log.debug("expireTime==={}",expireTime);

    }

    private static String innerSign(TokenUser tokenUser,String secret){
        Algorithm algorithm = Algorithm.HMAC256(secret);
        String token = JWT.create()
                .withClaim(CLAIM_NAME_USERID, tokenUser.getUserId())
                .withClaim(CLAIM_NAME_USERNAME, tokenUser.getUserName())
                .withExpiresAt(new Date(System.currentTimeMillis()+expireTime*1000))
                .sign(algorithm);
        return token;
    }

    //根据用户信息和密钥生成令牌(登录成功时使用)
    public static String sign(TokenUser tokenUser,String secret){

        String token = innerSign(tokenUser,secret);
        //登录时生成的令牌，既为键也为值，放入redis，过期时间为令牌过期时间的2倍
        stringRedisTemplate.opsForValue().set(token, token, expireTime*2, TimeUnit.SECONDS);

        return token;

    }

    //根据令牌获取用户信息
    public static TokenUser getTokenUser(String clientToken){
        if(!StringUtils.hasText(clientToken)){
            throw new NotAuthentificationException("令牌不存在！请登录");
        }
        DecodedJWT decodedJWT = null;
        try {
            decodedJWT = JWT.decode(clientToken);
        } catch (JWTDecodeException e) {
            throw new NotAuthentificationException("令牌错误！请登录");
        }

        String userId = decodedJWT.getClaim(CLAIM_NAME_USERID).asString();
        String userName = decodedJWT.getClaim(CLAIM_NAME_USERNAME).asString();

        if(!StringUtils.hasText(userId) || !StringUtils.hasText(userName)){
            throw new NotAuthentificationException("令牌不完整！请登录");
        }

        return new TokenUser(userId,userName);



    }

    //校验令牌
    public static TokenUser verify(String clientToken){

        TokenUser tokenUser = getTokenUser(clientToken);

        //以客户端令牌为键从redis中取缓存令牌
        String cacheToken = stringRedisTemplate.opsForValue().get(clientToken);
        if(!StringUtils.hasText(cacheToken)){//无缓存令牌
            throw new NotAuthentificationException("令牌失效！请登录");
        }



        String password = null;
        try {
            password = tokenDao.findPasswordByUserId(tokenUser.getUserId());
        } catch (Exception e) {
            e.printStackTrace();
            throw new NotAuthentificationException("密钥获取失败！请登录");
        }
        if(!StringUtils.hasText(password)){
            throw new NotAuthentificationException("令牌无密钥！请登录");
        }

        Algorithm algorithm = Algorithm.HMAC256(password);
        JWTVerifier verifier = JWT.require(algorithm).build();//获取验证器
        try {
            verifier.verify(cacheToken);//校验缓存令牌
            return tokenUser;
        } catch (TokenExpiredException e){//令牌过期，应当刷新令牌
            String newToken = innerSign(tokenUser,password);
            stringRedisTemplate.opsForValue().set(clientToken, newToken, expireTime*2, TimeUnit.SECONDS);
            return tokenUser;
        }catch (JWTVerificationException e) {//令牌非法
            throw new NotAuthentificationException("非法令牌！请登录");
        }
    }


    public static void removeToken(String clientToken){
        if(StringUtils.hasText(clientToken))
            stringRedisTemplate.delete(clientToken);
    }



}
